#!/usr/bin/env python2
# coding:utf-8

import os
import sys
import datetime
import functools
import inspect
import logging
import types
import uuid

import task_db
from task_exception import Failure


_now = datetime.datetime.now


logger = logging.getLogger("task_queue")
formatter = logging.Formatter('%(name)-13s %(asctime)s %(levelname)-8s %(message)s')
abs_path = os.path.dirname(os.path.realpath(__file__))
log_path = os.path.join(abs_path, '..', '..', 'log/task_queue.log')
file_handler = logging.FileHandler(log_path)

file_handler.setFormatter(formatter)
#stream_handler = logging.StreamHandler(sys.stderr)

logger.addHandler(file_handler)
#logger.addHandler(stream_handler)
#logger.removeHandler(stream_handler)

#log set level DEBUG，INFO，WARNING，ERROR，CRITICAL
logger.setLevel(logging.DEBUG)



def inject_now_method(method):
    global _now
    _now = method
    task_db.inject_now_method(method)


def _create(task_name, method, is_member, task_module, *args, **kwargs):
    now = _now()
    task_id = str(uuid.uuid4())
    process_pid = str(os.getpid())

    host = os.popen('hostname')
    try:
        hostname = host.read()
    except Failure as ex:
        fail(task_id)
        hostname = ''
    finally:
        host.close()

    logging.debug('Creating task %s at %s on host %s', task_id, now, hostname)
    task = {'id': task_id,
            'process_pid': process_pid,
            'hostname': hostname,
            'task_name': task_name,
            'task_module': task_module,
            'method': method,
            'is_member': is_member,
            'args': args,
            'kwargs': kwargs,
            'created_at': now,
            'updated_at': now,
            'is_active': False,
            'progress': None}
    task_db.task_create(task)
#     try:
# #         os.mknod('/opt/fusionstack/lich/task/%s.pid' % process_pid)
#         os.mknod('task/%s.pid' % process_pid)
#     except IOError:
# #         os.makedirs('/opt/fusionstack/lich/task')
#         os.makedirs('./task')
# #         os.mknod('/opt/fusionstack/lich/task/%s.pid' % process_pid)     
#         os.mknod('task/%s.pid' % process_pid)   
    return task_id


def _is_member(func, args):
    if not args:
        return False
    ismethod = False
    for item in inspect.getmro(type(args[0])):
        for x in inspect.getmembers(item):
            if 'im_func' in dir(x[1]):
                ismethod = x[1].im_func == func
                if ismethod:
                    break
        else:
            continue
        break
    return ismethod

    
def rtk(task_module=None, auto_update=True):
    def wrapper(func):
        @functools.wraps(func)
        def wrapped(*args, **kwargs):
            if 'task_id' in kwargs:
                task_id = kwargs.pop('task_id')
                progress = kwargs.pop('progress')
                if not auto_update:
                    return func(task_id=task_id, progress=progress, *args, **kwargs)

                try:
                    rv = func(task_id=task_id, progress=progress, *args, **kwargs)
                except Failure as ex:
                    fail(task_id, ex.progress)
                    return ex.progress
                except Exception as ex:
                    fail(task_id, None)
                    raise
                if not isinstance(rv, types.GeneratorType):
                    update(task_id, rv)
                    finish(task_id)
                    return rv

                def gen():
                    try:
                        for orig_rv in rv:
                            update(task_id, orig_rv)
                            yield orig_rv
                    except Failure as ex:
                        fail(task_id, ex.progress)
                        yield ex.progress
                    except Exception as ex:
                        fail(task_id, None)
                        raise StopIteration
                    finish(task_id)

                return gen()

            else:
                task_name = func.__name__
                if _is_member(wrapped, args):
                    method = task_name
                    is_member = True
                else:
                    method = wrapped
                    is_member = False
                task_id = _create(task_name, method, is_member, task_module,
                                  *args, **kwargs)
                return task_id
        return wrapped
    return wrapper


def get(task_id):
    return task_db.task_get(task_id)


def request(task_module=None):
    try:
        return task_db.task_pop(task_module)['id']
    except IndexError:
        return None


def timeout(time, task_name=None):
    return task_db.task_timeout(time, task_name)


def run(task_id):
    task = task_db.task_get(task_id)
    if task['is_member']:
        method = getattr(task['args'][0], task['method'])
    else:
        method = task['method']
    task_db.task_start(task_id)
    return method(task_id=task['id'], progress=task['progress'],
                  *task['args'], **task['kwargs'])


def fail(task_id, progress=None):
    now = _now()
    values = {}
    values['updated_at'] = now
    values['is_active'] = False
    if progress:
        values['progress'] = progress
    task_db.task_update(task_id, values)
    logger.error('Failed task %s at %s', task_id, now)


def update(task_id, progress):
    now = _now()
    values = {}
    values['updated_at'] = now
    values['progress'] = progress
    task_db.task_update(task_id, values)
    logger.debug('Updated task %s at %s', task_id, now)


def finish(task_id):
    values = {}
    values['updated_at'] = _now()
    values['completed_at'] = _now()
    values['is_active'] = False
    task_db.task_update(task_id, values)
    logger.debug('Finished task %s', task_id)


def is_active(task_id):
    try:
        return task_db.task_get(task_id)['is_active']
    except task_db.TaskNotFound:
        return False


def is_complete(task_id):
    try:
        return task_db.task_get(task_id)['completed_at'] is not None
    except task_db.TaskNotFound:
        return False


def exists(task_id):
    try:
        task_db.task_get(task_id)
        return True
    except task_db.TaskNotFound:
        return False


def setup_db(sql_connection='sqlite://task.sqlite'):
    task_db.connect(sql_connection)

 
if __name__ == 'task':
    setup_db('sqlite:///task.sqlite')
